慕课网办公OA平台
课程介绍
- 需求说明与环境准备
- 开发基于RBAC的访问控制模块
- 开发多级请假审批流程
办公自动化OA系统
- 办公自动化系统(Office Automation)是替代传统办公的解决方案
- OA系统是利用软件技术构建的单位内部办公平台,用于辅助办公
- 利用OA系统可将办公数据数字化,可扩大提高办公流程执行效率
项目需求
- 慕课网办公OA系统要求采用多用户B/S架构设计开发
- HR为每一位员工分配系统账户,员工用此账户登录系统
- 公司采用分级定岗,从1-8依次提升,不同岗位薪资水平不同
- 6级(含)以下员工为业务岗,对应人员执行公司业务事宜
- 7-8级为管理岗,其中7级为部门经理,8级为总经理
- 业务岗与管理岗员工可用系统功能不同,要求允许灵活配置
请假流程
- 公司所有员工都可以使用”请假申请”功能申请休假
- 请假时间少于72小时,部门经理审批后直接通过
- 请假时间大于72小时,部门经理审批后还需总经理进行审批
- 部门经理只允许批准本部门员工申请
- 部门经理请假需直接由总经理审批
- 总经理提起请假申请,系统自动批准通过
搭建基础架构
框架&组件
- MySQL 8
- Mybatis 3.5
- Alibaba Druid
- Servlet 3.1
- Freemarker 2.3
- LayUl 2.5
工程结构
imooc-oa eclipse工程项目
/src - java源代码目录
/WebContent - Web资源目录
/css - css文件目录
/js - js文件目录
/image - 图片资源目录
/upload - 上传文件目录
/WEB-INF //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
/jsp - jsp页面目录
/lib - jar文件目录
/classes - 编译后的class目录
/web.xml web描述符文件
包结构
com.imooc-oa //逆命名法
/controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
/service - 存放处理逻辑类model[伪数据库] //完成业务逻辑 service与dao进行传递调用
/dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
/entity - 存放实体类 JavaBean java中的简单对象
/utils - 通用工具类 底层通用的工具类或方法
环境配置

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客
配置pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>imooc-oa</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<dependencies>
<!--Mybatis 框架-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--MySQL 8 JDBC驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--Druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<!--Junit4单元测试框架-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!--只参与Maven Test,不进行发布-->
<scope>test</scope>
</dependency>
<!--Logback日志输出组件-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--Freemarker依赖-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency>
<!--servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--依赖只参与编译测试,不进行发布-->
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--用Maven时必打的代码-->
<!--利用Maven编译插件将编译级别提高至1.8,解决lambda表达式错误-->
<groupId>org.apache.maven.plugins</groupId>
<!--maven-compiler-plugin是Maven自带的编译插件-->
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<!--检查源码采用1.8规则,默认为1.5-->
<source>1.8</source>
<!--按1.8规则生成字节码-->
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
配置数据库连接池
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--开启驼峰命名转换 form_id -> formId-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="dev">
<!--开发环境配置-->
<environment id="dev">
<!--事务管理器采用JDBC方式-->
<transactionManager type="JDBC"></transactionManager>
<!--利用Mybatis自带连接池管理连接-->
<dataSource type="POOLED">
<!--MyBatis与Druid的整合-->
<!--JDBC连接属性-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/test.xml"/>
</mappers>
</configuration>
开发Mybatis
Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客
test.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="sample" resultType="string">
select 'success'
</select>
</mapper>
util-MybatisUtils.java
package util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
import java.util.function.Function;
public class MybatisUtils {
//利用static(静态)属于类不属于对象,且全局唯一
private static SqlSessionFactory sqlSessionFactory = null;
//利用静态块在初始化类时实例化sqlSessionFactory
static{
Reader reader = null;
try{
reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}catch(IOException e){
//初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
throw new ExceptionInInitializerError(e);
}
}
/**
* 执行SELECT查询SQL
* @param func 要执行查询语句的代码块
* @return 查询结果
*/
//用于数据的查询[极大的简化查询] mybatis执行SQL时一定要有mapper的xml
public static Object executeQuery(Function<SqlSession,Object> func){ //函数式接口
SqlSession sqlSession = sqlSessionFactory.openSession();
try{//具体查询交给Function实现 查询前完成连接的打开和关闭
Object obj = func.apply(sqlSession);
return obj;
}finally {
sqlSession.close(); //最后一步释放连接资源
}
}
/**
* 执行INSERT/UPDATE/DELETE写操作SQL
* @param func 要执行的写操作代码块
* @return 写操作后返回的结果
*/
public static Object executeUpdate(Function<SqlSession,Object> func){ //函数式接口
SqlSession sqlSession = sqlSessionFactory.openSession(false);
try{//具体查询交给Function实现 查询前完成连接的打开和关闭
Object obj = func.apply(sqlSession);
sqlSession.commit();
return obj;
}catch (RuntimeException e){
sqlSession.rollback();
throw e;
}finally {
sqlSession.close(); //最后一步释放连接资源
}
}
}
MybatisUtilsTestor.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import util.MybatisUtils;
public class MybatisUtilsTestor {
// @Test
// public void testcase1(){
// String result = (String)MybatisUtils.executeQuery(sqlSession -> {
// String out = (String)sqlSession.selectOne("test.sample");
// return out; //out会被retrun obj接收 返回Object
// });
// System.out.println(result);
// }
@Test
public void testcase2(){
String result = (String) MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
System.out.println(result);
}
}
MyBatis整合Druid连接池 (自定义连接池)
重新整合mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--开启驼峰命名转换 form_id -> formId-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="dev">
<!--开发环境配置-->
<environment id="dev">
<!--事务管理器采用JDBC方式-->
<transactionManager type="JDBC"></transactionManager>
<!--利用Mybatis自带连接池管理连接-->
<!-- <dataSource type="POOLED">-->
<dataSource type="com.imooc.oa.datasource.DruidDataSourceFactory">
<!--MyBatis与Druid的整合-->
<!--JDBC连接属性-->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!--连接池初始连接数-->
<property name="initialSize" value="10"/>
<!--连接池最大连接数-->
<property name="maxActive" value="20"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/test.xml"/>
</mappers>
</configuration>
com.imooc.oa.datasource.DruidDataSourceFactory
package com.imooc.oa.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
public DruidDataSourceFactory(){ //1.创造空的数据源对象
// 2.调用setProperties读取xml对dataSource属性源进行设置
this.dataSource = new DruidDataSource(); //表达数据源信息
}
//3.数据源需要额外设置要重写
@Override
public DataSource getDataSource() { //获取已经初始化的连接池进行返回
try {
((DruidDataSource)this.dataSource).init(); //初始化Druid数据源
} catch (SQLException e) {
throw new RuntimeException(e);
}
return this.dataSource;
}
}
Ctrl + Shift + N 文件查找对话框
整合Freemarker
pom.xml
<!--Freemarker依赖-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency>
<!--servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--依赖只参与编译测试,不进行发布-->
<scope>provided</scope>
</dependency>
web-WEB-INF-ftl-test.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>${result}</h1>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>freemaker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
<init-param>
<!-- 定义模板的存储路径-->
<param-name>TemplatePath</param-name>
<param-value>/WEB-INF/ftl</param-value>
</init-param>
<init-param>
<!-- default_encoding用于设置读取ftl文件时采用的字符集,进而避免中文乱码的产生-->
<param-name>default_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>freemaker</servlet-name>
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
</web-app>
TestServlet.java
package com.imooc.oa.test;
import com.imooc.oa.util.MybatisUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "TestServlet", urlPatterns = "/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String result = (String)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
req.setAttribute("result",result);
req.getRequestDispatcher("/test.ftl").forward(req,resp);
}
}
RBAC(Role-Based Access Control)介绍
基于**角色权限控制**(RBAC)是面向企业安全策略的访问控制方式
RBAC核心思想是将控制访问的资源与角色(Role)进行绑定
系统的用户(User)与角色(Role)再进行绑定, 用户便拥有对应权限
一般主键cno或id都要设定字段类型为 BigInt
imooc-oa.sql
实现用户登录
基于LayUI开发登录页
LayUI前端框架
Layui - 经典开源模块化前端 UI 框架(官网文档镜像站) (layuiweb.com)
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>办公OA系统</title>
<link rel="stylesheet" href="/resources/layui-main/src/css/layui.css">
<style>
body {
background-color: #f2f2f2;
}
.oa-container {
/*background-color: white;*/
position: absolute;
width: 400px;
height: 350px;
top: 50%;
left: 50%;
padding: 20px;
margin-left: -200px;
margin-top: -175px;
}
#username,#password{
/*text-align: center;*/
/*font-size: 24px;*/
}
</style>
</head>
<body>
<div class="oa-container">
<h1 style="text-align: center; margin-bottom: 20px">办公OA系统</h1>
<form class="layui-form">
<div class="layui-form-item">
<input type="text" id="username" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-item">
<input type="password" id="password" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
</div>
</form>
</div>
</body>
</html>
实现用户登录-1
com.imooc.oa.entity.User
package com.imooc.oa.entity;
public class User {
/*
<settings>
<!--开启驼峰命名转换 form_id -> formId-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
*/
private Long userId;
private String username;
private String password;
private Long employeeId;
Getter + Setter
}
com.imooc.oa.dao.UserDao
package com.imooc.oa.dao;
import com.imooc.oa.entity.User;
import com.imooc.oa.util.MybatisUtils;
/**
* 用户表
*/
public class UserDao {
/**
* 按照用户名查询用户表
* @param username 用户名
* @return User对象包含对应的用户信息,null则代表对象不存在
*/
public User selectByUsername(String username){
User user = (User)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("usermapper.selectByUsername",username));
return user;
}
}
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="usermapper">
<select id="selectByUsername" parameterType="String" resultType="com.imooc.oa.entity.User">
select * from sys_user where username = #{value}
</select>
</mapper>
mybatis-config.xml
<mappers>
<mapper resource="mappers/test.xml"/>
<mapper resource="mappers/user.xml"/>
</mappers>
Dao → Service
创建测试用例快捷键 Ctrl+Shift+T
UserSerive.java
package com.imooc.oa.serive;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.User;
import com.imooc.oa.serive.exception.BussinessException;
public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
private UserDao userDao = new UserDao(); //实例化
/**
* 根据前台输入进行登录校验
* @param username 前台输入的用户名
* @param password 前台输入的密码
* @return 校验通过后,包含对应用户数据的User实体类
* @throws BussinessException L001-用户名不存在,L002-密码错误
*/
public User checkLogin(String username, String password){
User user = userDao.selectByUsername(username);
if (user == null){
//抛出用户不存在异常
throw new BussinessException("L001", "用户名不存在");
}
if(!password.equals(user.getPassword())){
throw new BussinessException("L002", "密码错误");
}
return user;
}
}
test/serive.UserServiceTest.java
package com.imooc.oa.serive;
import junit.framework.TestCase;
import org.junit.Test;
public class UserServiceTest extends TestCase {
private UserService userService = new UserService();
@Test
public void testCheckLogin1() {
userService.checkLogin("uu","1234");
}
@Test
public void testCheckLogin2() {
userService.checkLogin("m8","1234");
}
@Test
public void testCheckLogin3() {
userService.checkLogin("uu","test");
}
}
serive.exception.BussinessException.java
package com.imooc.oa.serive.exception;
/**
* 业务逻辑异常
*/
public class BussinessException extends RuntimeException{
private String code; //异常编码,异常的以为标识
private String message; //异常的具体文本消息
public BussinessException(String code, String msg){
super(code + ":" + msg);
this.code = code;
this.message = msg;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
实现用户登录-2
com.imooc.oa.controller.LoginServlet.java
package com.imooc.oa.controller;
import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
Logger logger = LoggerFactory.getLogger(LoginServlet.class);
private UserService userService = new UserService();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//接收用户输入
String username = request.getParameter("username");
String password = request.getParameter("password");
Map<String, Object> result = new HashMap<>();
try {
//调用业务逻辑
User user = userService.checkLogin(username, password);
result.put("code", "0");
result.put("message", "success");
}catch (BussinessException ex){
logger.error(ex.getMessage() , ex);
result.put("code", ex.getCode());
result.put("message", ex.getMessage());
}catch (Exception ex){
logger.error(ex.getMessage() , ex);
result.put("code", ex.getClass().getSimpleName());
result.put("message", ex.getMessage());
}
//返回对应结果
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
实现用户登录-3
pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
login.xml实现增添表单校验
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>慕课网办公OA系统</title>
<link rel="stylesheet" href="/resources/layui/css/layui.css">
<style>
body{
background-color: #F2F2F2;
}
.oa-container{
/*background-color: white;*/
position: absolute;
width: 400px;
height: 350px;
top: 50%;
left: 50%;
padding: 20px;
margin-left: -200px;
margin-top: -175px;
}
#username,#password{
/*text-align: center;*/
/*font-size: 24px;*/
}
</style>
</head>
<body>
<div class="oa-container">
<h1 style="text-align: center;margin-bottom: 20px">办公OA系统</h1>
<form class="layui-form">
<div class="layui-form-item">
<input type="text" id="username" lay-verify="required" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input" >
</div>
<div class="layui-form-item">
<input type="password" id="password" lay-verify="required" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input" >
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
</div>
</form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script>
// 表单提交事件 表单输入校验 在上面加 lay-verify="requrired"
layui.form.on("submit(login)" , function(formdata){//data参数包含了当前表单的数据
console.log(formdata);
//发送ajax请求进行登录校验
layui.$.ajax({
url : "/check_login",
data : formdata.field, //提交表单数据
type : "post",
dataType : "json" ,
success : function(json){
console.log(json);
if(json.code == "0"){ //登录校验成功 内置弹出层
layui.layer.msg("登录成功");
}else{
layui.layer.msg(json.message);
}
}
})
return false;//submit提交事件返回true则表单提交,false则阻止表单提交
})
</script>
</body>
</html>
</html>
后面通过Ajax[浏览器后台(上面return false)]请求向服务器发起异步通信获取校验是否通过
分析后台首页布局与设计
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>办公OA系统</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
<!--头部导航栏-->
<div class="layui-header">
<!--系统标题-->
<div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
<!--右侧当前用户信息-->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:void(0)">
<!--图标-->
<span class="layui-icon layui-icon-user" style="font-size: 20px">
</span>
<!--用户信息-->
姓名[部门-职务]
</a>
</li>
<!--注销按钮-->
<li class="layui-nav-item"><a href="#">注销</a></li>
</ul>
</div>
<!--左侧菜单栏-->
<div class="layui-side layui-bg-black">
<!--可滚动菜单-->
<div class="layui-side-scroll">
<!--可折叠导航栏-->
<ul class="layui-nav layui-nav-tree">
<!--父节点-->
<li class="layui-nav-item layui-nav-itemed">
<a href="javascript:void(0)">模块1</a>
<dl class="layui-nav-child module" data-node-id="1"></dl>
</li>
<!--子节点-->
<dd class="function" data-parent-id="1">
<a href="javascript:void(0)" target="ifmMain">功能1</a>
</dd>
<dd class="function" data-parent-id="1">
<a href="javascript:void(0)" target="ifmMain">功能2</a>
</dd>
<dd class="function" data-parent-id="1">
<a href="javascript:void(0)" target="ifmMain">功能3</a>
</dd>
<li class="layui-nav-item layui-nav-itemed">
<a href="javascript:void(0)">模块2</a>
<dl class="layui-nav-child module" data-node-id="2"></dl>
</li>
<dd class="function" data-parent-id="2">
<a href="javascript:void(0)" target="ifmMain">功能3</a>
</dd>
<dd class="function" data-parent-id="2">
<a href="javascript:void(0)" target="ifmMain">功能4</a>
</dd>
<dd class="function" data-parent-id="2">
<a href="javascript:void(0)" target="ifmMain">功能5</a>
</dd>
</ul>
</div>
</div>
<!--主体部分采用iframe嵌入其他页面-->
<div class="layui-body" style="overflow-y: hidden">
<iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
</div>
<!--版权信息-->
<div class="layui-footer">
Copyright © imooc. All Rights Reserved.
</div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
//将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
layui.$(".function").each(function () {
var func = layui.$(this);
var parentId = func.data("parent-id");
layui.$("dl[data-node-id=" + parentId + "]").append(func);
})
//刷新折叠菜单
layui.element.render('nav');
</script>
</body>
</html>
动态显示功能菜单-1 【核心:rbac.xml】
通过用户找到角色sys_user 再通过角色找到节点sys_role_user 接下来通过节点编号sys_role_node去获取与之对应的节点其他信息(三表关联)
xml→Dao→UserService
resources.mappers.rbac.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="rbacmapper">
<select id="selectNodeByUserId" parameterType="Long" resultType="com.imooc.oa.entity.Node">
select distinct n.*
from
sys_role_user ru, sys_role_node rn, sys_node n
where
ru.role_id = rn.role_id and user_id = #{value} and rn.node_id = n.node_id
order by n.node_code
</select>
</mapper>
mybatis-config.xml
<mappers>
<mapper resource="mappers/test.xml"/>
<mapper resource="mappers/user.xml"/>
<mapper resource="mappers/rbac.xml"/>
</mappers>
imooc.oa.dao.RbacDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.util.MybatisUtils;
import java.util.List;
public class RbacDao {
public List<Node> selectNodeByUserId(Long userId){
return (List)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectList("rbacmapper.selectNodeByUserId", userId));
}
}
service.UserService.java
package com.imooc.oa.service;
import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;
import java.util.List;
public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
private UserDao userDao = new UserDao(); //实例化
private RbacDao rbacDao = new RbacDao();
/**
* 根据前台输入进行登录校验
* @param username 前台输入的用户名
* @param password 前台输入的密码
* @return 校验通过后,包含对应用户数据的User实体类
* @throws BussinessException L001-用户名不存在,L002-密码错误
*/
public User checkLogin(String username, String password){
User user = userDao.selectByUsername(username);
if (user == null){
//抛出用户不存在异常
throw new BussinessException("L001", "用户名不存在");
}
if(!password.equals(user.getPassword())){
throw new BussinessException("L002", "密码错误");
}
return user;
}
public List<Node> selectNodeByUserId(Long userId){
List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
return nodeList;
}
}
UserServiceTest.java
@Test
public void selectNodeByUserId(){
List<Node> nodeList = userService.selectNodeByUserId(2l);
System.out.println(nodeList);
}
Node.java
public class Node {
private Long nodeId;
private Integer nodeType;
private String nodeName;
private String url;
private Integer nodeCode;
private Long parentId;
Setter + Getter
}
动态显示功能菜单-2 (不同用户登录不同功能)
修改LoginServlet.java
package com.imooc.oa.controller;
import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
Logger logger = LoggerFactory.getLogger(LoginServlet.class);
private UserService userService = new UserService();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//接收用户输入
String username = request.getParameter("username");
String password = request.getParameter("password");
Map<String, Object> result = new HashMap<>();
try {
//调用业务逻辑 不能直接 request.setAttribute 选更大的对象
User user = userService.checkLogin(username, password);
HttpSession session = request.getSession();
//session种存在用户信息 向session存入登录用户信息,属性名:login_user
session.setAttribute("login_user", user);
result.put("code", "0");
result.put("message", "success");
result.put("redirect_url", "/index"); //url登陆成功直接返回客户端
}catch (BussinessException ex){
logger.error(ex.getMessage() , ex);
result.put("code", ex.getCode());
result.put("message", ex.getMessage());
}catch (Exception ex){
logger.error(ex.getMessage() , ex);
result.put("code", ex.getClass().getSimpleName());
result.put("message", ex.getMessage());
}
//返回对应结果
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
oa.controller.IndexServlet.java
package com.imooc.oa.controller;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
//接收数据传入
@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
private UserService userService = new UserService();
protected void doPost(HttpServletRequest request, HttpServletResponse response){
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("login_user");
List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
request.setAttribute("node_list",nodeList);
request.getRequestDispatcher("/index.ftl").forward(request, response);
}
}
将index.html 变成 index.ftl并放在Web/WEB-INF/ftl/index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>办公OA系统</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
<!--头部导航栏-->
<div class="layui-header">
<!--系统标题-->
<div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
<!--右侧当前用户信息-->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:void(0)">
<!--图标-->
<span class="layui-icon layui-icon-user" style="font-size: 20px">
</span>
<!--用户信息-->
姓名[部门-职务]
</a>
</li>
<!--注销按钮-->
<li class="layui-nav-item"><a href="#">注销</a></li>
</ul>
</div>
<!--左侧菜单栏-->
<div class="layui-side layui-bg-black">
<!--可滚动菜单-->
<div class="layui-side-scroll">
<!--可折叠导航栏-->
<ul class="layui-nav layui-nav-tree">
<#list node_list as node>
<!--父节点-->
<#if node.nodeType == 1>
<li class="layui-nav-item layui-nav-itemed">
<a href="javascript:void(0)">${node.nodeName}</a>
<dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
</li>
</#if>
<#if node.nodeType == 2>
<!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
<a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
</dd>
</#if>
</#list>
</ul>
</div>
</div>
<!--主体部分采用iframe嵌入其他页面-->
<div class="layui-body" style="overflow-y: hidden">
<iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
</div>
<!--版权信息-->
<div class="layui-footer">
Copyright © imooc. All Rights Reserved.
</div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
//将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
layui.$(".function").each(function () {
var func = layui.$(this);
var parentId = func.data("parent-id");
layui.$("dl[data-node-id=" + parentId + "]").append(func);
})
//刷新折叠菜单
layui.element.render('nav');
</script>
</body>
</html>
Xml配置下实现Mapper接口 (登录用户所对应员工)
接口 xml mapper增加 employeeservice
index.ftl indexServlet 改index.ftl
增加自动化部门 entity.Department dao.创建接口DepartmentDao mappers.department.xml -config.xml注册 DepartmentSerive.java
IndexServlet.java index.ftl
entity.Employee.java
public class Employee{
private Long employeeId;
private String name;
private Long departmentId;
private String title;
private Integer level;
Getter+Setter
}
dao.EmployeeDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.Department;
public interface DepartmentDao {
public Department selectById(Long departmentId);
}
mybatis-config.xml
<mapper resource="mappers/employee.xml"/>
index.ftl
<!--右侧当前用户信息-->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:void(0)">
<!--图标-->
<span class="layui-icon layui-icon-user" style="font-size: 20px">
</span>
<!--用户信息-->
${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
</a>
</li>
IndexServlet.java
package com.imooc.oa.controller;
import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
private UserService userService = new UserService();
private EmployeeService employeeService = new EmployeeService();
private DepartmentService departmentService = new DepartmentService();
protected void doPost(HttpServletRequest request, HttpServletResponse response){
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
//得到当前登录用户对象
User user = (User) session.getAttribute("login_user");
Employee employee = employeeService.selectById(user.getEmployeeId());
//获取登录用户可用功能模块列表
List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
//获取员工对应的部门
Department department = departmentService.selectById(employee.getDepartmentId());
//放入请求属性 session生存时间长
request.setAttribute("node_list",nodeList);
session.setAttribute("current_employee",employee);
session.setAttribute("current_department", department);
//请求派发至ftl进行展现
request.getRequestDispatcher("/index.ftl").forward(request, response);
}
}
增加自动化部门
entity.Department.java
public class Department {
private Long departmentId;
private String departmentName;
Getter+Setter
}
dao.DepartmentDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.Department;
public interface DepartmentDao {
public Department selectById(Long departmentId);
}
mapper.department.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.DepartmentDao">
<!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
<select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Department">
select * from adm_department where department_id = #{value}
</select>
</mapper>
mybatis-config.xml
<mapper resource="mappers/department.xml"/>
service.DepartmentServlet.java
package com.imooc.oa.service;
import com.imooc.oa.dao.DepartmentDao;
import com.imooc.oa.entity.Department;
import com.imooc.oa.util.MybatisUtils;
public class DepartmentService {
/**
* 按编号得到部门对象
* @param departmentId 部门编号
* @return 部门对象,null代表部门不存在
*/
public Department selectById(Long departmentId){
return (Department) MybatisUtils.executeQuery(
sqlSession -> sqlSession.getMapper(DepartmentDao.class).selectById(departmentId));
}
}
controller.indexServlet.java
package com.imooc.oa.controller;
import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
private UserService userService = new UserService();
private EmployeeService employeeService = new EmployeeService();
private DepartmentService departmentService = new DepartmentService();
protected void doPost(HttpServletRequest request, HttpServletResponse response){
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
//得到当前登录用户对象
User user = (User) session.getAttribute("login_user");
Employee employee = employeeService.selectById(user.getEmployeeId());
//获取登录用户可用功能模块列表
List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
//获取员工对应的部门
Department department = departmentService.selectById(employee.getDepartmentId());
//放入请求属性 session生存时间长
request.setAttribute("node_list",nodeList);
session.setAttribute("current_employee",employee);
session.setAttribute("current_department", department);
//请求派发至ftl进行展现
request.getRequestDispatcher("/index.ftl").forward(request, response);
}
}
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>办公OA系统</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
<!--头部导航栏-->
<div class="layui-header">
<!--系统标题-->
<div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
<!--右侧当前用户信息-->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:void(0)">
<!--图标-->
<span class="layui-icon layui-icon-user" style="font-size: 20px">
</span>
<!--用户信息-->
${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
</a>
</li>
<!--注销按钮-->
<li class="layui-nav-item"><a href="#">注销</a></li>
</ul>
</div>
<!--左侧菜单栏-->
<div class="layui-side layui-bg-black">
<!--可滚动菜单-->
<div class="layui-side-scroll">
<!--可折叠导航栏-->
<ul class="layui-nav layui-nav-tree">
<#list node_list as node>
<!--父节点-->
<#if node.nodeType == 1>
<li class="layui-nav-item layui-nav-itemed">
<a href="javascript:void(0)">${node.nodeName}</a>
<dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
</li>
</#if>
<#if node.nodeType == 2>
<!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
<a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
</dd>
</#if>
</#list>
</ul>
</div>
</div>
<!--主体部分采用iframe嵌入其他页面-->
<div class="layui-body" style="overflow-y: hidden">
<iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
</div>
<!--版权信息-->
<div class="layui-footer">
Copyright © imooc. All Rights Reserved.
</div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
//将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
layui.$(".function").each(function () {
var func = layui.$(this);
var parentId = func.data("parent-id");
layui.$("dl[data-node-id=" + parentId + "]").append(func);
})
//刷新折叠菜单
layui.element.render('nav');
</script>
</body>
</html>
基于MD5算法对密码加密
MD5摘要算法
- MD5信息摘要算法广泛使用的密码散列函数
- MD5可以产生出一个128位的散列值用于唯一标识源数据
- 项目中通常使用MD5作为敏感数据的加密算法
MD5特点
- 压缩性, MD5生成的摘要长度固定
- 抗修改, 源数据哪怕有一个字节变化, MD5也会有巨大差异
- 不可逆, 无法通过MD5反向推算源数据
Apache Commons Codec
- Commons-Codec是Apache提供的编码/解码组件
- 通过Commons-Codec可以轻易生成源数据的MD5摘要
- MD5摘要方法: String md5 = DigestUtils.md5Hex (源数据)
util.MD5Utils.java
package com.imooc.oa.util;
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Utils {
public static String md5Digest(String source){
return DigestUtils.md5Hex(source);
}
}
敏感数据加盐混淆
md5utilstest userservice user
public class User {
/*
<settings>
<!--开启驼峰命名转换 form_id -> formId-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
*/
private Long userId;
private String username;
private String password;
private Long employeeId;
private Integer salt;
Getter + Setter
}
util.MD5Utils.java
package com.imooc.oa.util;
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Utils {
/**
* 对数据源加盐混淆后生成MD5摘要
* @param source 源数据
* @return MD5摘要
*/
public static String md5Digest(String source){
return DigestUtils.md5Hex(source);
}
public static String md5Digest(String source, Integer salt){
char[] ca = source.toCharArray(); //字符数组
for (int i = 0; i < ca.length; i++) {
ca[i] = (char)(ca[i] + salt);
}
String target = new String(ca);
// System.out.println(target);
String md5 = DigestUtils.md5Hex(target);
return md5;
}
public static void main(String[] args) {
System.out.println(MD5Utils.md5Digest("test", 188));
}
}
修改UserService.java 中的密码校验
package com.imooc.oa.service;
import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MD5Utils;
import java.util.List;
public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
private UserDao userDao = new UserDao(); //实例化
private RbacDao rbacDao = new RbacDao();
/**
* 根据前台输入进行登录校验
* @param username 前台输入的用户名
* @param password 前台输入的密码
* @return 校验通过后,包含对应用户数据的User实体类
* @throws BussinessException L001-用户名不存在,L002-密码错误
*/
public User checkLogin(String username, String password){
User user = userDao.selectByUsername(username);
if (user == null){
//抛出用户不存在异常
throw new BussinessException("L001", "用户名不存在");
}
//对前台输入的密码加盐混淆后生成MD5,与保存在数据库中的MD5密码进行对比
String md5 = MD5Utils.md5Digest(password, user.getSalt());
if(!md5.equals(user.getPassword())){
throw new BussinessException("L002", "密码错误");
}
return user;
}
public List<Node> selectNodeByUserId(Long userId){
List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
return nodeList;
}
}
实现注销功能
loginServlet保存着数据 indexservlet中的session保存着数据
清除session
oa.controller.LogoutServlet.java
package com.imooc.oa.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "LogoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate(); //会话注销
response.sendRedirect("/login.html"); //跳转回去
}
}
index.ftl
<!--注销按钮-->
<li class="layui-nav-item"><a href="/logout">注销</a></li>
请假流程数据库设计
开发多级审批流程
- 公司所有员工都可以使用”请假申请”功能申请休假
- 请假时间少于72小时,部门经理审批后直接通过
- 请假时间大于72小时,部门经理审批后还需总经理进行审批
- 部门经理只允许批准本部门员工申请
- 部门经理请假需直接由总经理审批
- 总经理提起请假申请,系统自动批准通过
工作流程表设计
请假单表LeaveForm → 审批任务流程表ProcessFlow → 消息通知表Notice
设计约束
每一个请假单对应一个审批流程
请假单创建后, 按业务规则生成部门经理、总经理审批任务
审批任务的经办人只能审批自己辖区内的请假申请(总裁办可以审批所有 软件只能审批软件)
所有审批任务”通过”, 代表请假已经批准
任意审批任务”驳回”操作, 其余审批任务取消, 请假申请驳回
请假流程中注意节点产生的操作都要生成对应的系统通知

实现Dao与数据交互
entity 数据新增接口dao(依靠Mybatis) 增加mapper 创造LeaveFormDaoTest
ProcessFlowDao + Test mapper
NoticeDao
entity.LeaveForm.java
public class LeaveForm {
private Long formId;
private Long employeeId;
private Integer formType;
private Date startTime;
private Date endTime;
private String reason;
private Date createTime;
private String state;
Getter + Setter
}
dao.LeaveForm.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.LeaveForm;
public interface LeaveFormDao {
public void insert(LeaveForm form);
}
mybatis-config.xml
<mapper resource="mappers/leave_form.xml"/>
test.dao.LeaveFormDaoTest.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class LeaveFormDaoTest extends TestCase {
@Test
public void testInsert(){
MybatisUtils.executeQuery(sqlSession -> {
LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
LeaveForm form = new LeaveForm();
form.setEmployeeId(4L);//员工编号
form.setFormType(1); //事假
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date startTime = null; //起始时间
Date endTime = null; //结束时间
try {
startTime = sdf.parse("2020-03-25 08:00:00");
endTime = sdf.parse("2020-04-01 18:00:00");
} catch (ParseException e) {
throw new RuntimeException(e);
}
form.setStartTime(startTime);
form.setEndTime(endTime);
form.setReason("回家探亲"); //请假事由
form.setCreateTime(new Date()); //创建时间
form.setState("processing"); //当前状态
dao.insert(form);
return null;
});
}
}
entity.ProcessFlow.java
package com.imooc.oa.entity;
import java.util.Date;
public class ProcessFlow {
private Long processId;
private Long formId;
private Long operatorId;
private String action;
private String result;
private String reason;
private Date createTime;
private Date auditTime;
private Integer orderNo;
private String state;
private Integer isLast;
Setter + Getter
}
dao.ProcessFlowDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.ProcessFlow;
public interface ProcessFlowDao {
public void insert(ProcessFlow processFlow);
}
mybatis-config.xml
<mapper resource="mappers/process_flow.xml"/>
test.dao.ProcessFlowDaoTest.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import java.util.Date;
public class ProcessFlowDaoTest extends TestCase {
public void testInsert(){
MybatisUtils.executeQuery(sqlSession -> {
ProcessFlowDao dao = sqlSession.getMapper(ProcessFlowDao.class);
ProcessFlow flow = new ProcessFlow();
flow.setFormId(31L);
flow.setOperatorId(21L);
flow.setAction("audit");
flow.setReason("approved");
flow.setReason("同意");
flow.setCreateTime(new Date());
flow.setAuditTime(new Date());
flow.setOrderNo(1);
flow.setState("ready");
flow.setIsLast(1);
dao.insert(flow);
return null;
});
}
}
entity.Notice.java
public class Notice {
private Long noticeId;
private Long receiverId;
private String content;
private Date createTime;
Getter + Setter
}
dao.NoticeDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.Notice;
public interface NoticeDao {
public void insert(Notice notice);
}
mybatis-config.xml
<mapper resource="mappers/notice.xml"/>
test.dao.NoticeDaoTest.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;
import java.util.Date;
public class NoticeDaoTest extends TestCase {
@Test
public void testInsert(){
MybatisUtils.executeQuery(sqlSession -> {
NoticeDao dao = sqlSession.getMapper(NoticeDao.class);
Notice notice = new Notice();
notice.setReceiverId(21L);
notice.setContent("测试消息");
notice.setCreateTime(new Date());
dao.insert(notice);
return null;
});
}
}
实现请假申请业务逻辑-1
LeaveFormService employee.xml{动态审批sql}
employee.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.EmployeeDao">
<!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
<select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Employee">
select * from adm_employee where employee_id = #{value}
</select>
<select id="selectLeader" parameterType="com.imooc.oa.entity.Employee" resultType="com.imooc.oa.entity.Employee">
select * from adm_employee where
<if test="emp.level < 7">
level = 7 and department_id = #{emp.departmentId}
</if>
<if test="emp.level == 7">
level = 8;
</if>
<if test="emp.level == 8">
employee_id = #{emp.employeeId}
</if>
</select>
</mapper>
service.LeaveFormService.java (重要业务走向代码不放在程序中)
package com.imooc.oa.service;
import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;
import java.util.Date;
/**
* 请假单流程服务
*/
public class LeaveFormService {
/**
* 创建请假单
* @param form 前端输入的请假单数据
* @return 持久化后的请假单对象
*/
public LeaveForm createLeaveForm(LeaveForm form){
LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
//1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
Employee employee = employeeDao.selectById(form.getEmployeeId());
if (employee.getLevel() == 8){
form.setState("approved");
}else {
form.setState("processing");
}
LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
leaveFormDao.insert(form);
//2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
ProcessFlow flow1 = new ProcessFlow();
flow1.setFormId(form.getFormId());
flow1.setOperatorId(employee.getEmployeeId());
flow1.setAction("apply");
flow1.setCreateTime(new Date());
flow1.setOrderNo(1);
flow1.setState("complete");
flow1.setIsLast(0);
processFlowDao.insert(flow1);
//3.分情况创建其余流程数据 employee.xml(动态sql语句)
//3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
if (employee.getLevel() < 7){
//动态sql EmployeeDao
Employee dmanage = employeeDao.selectLeader(employee);
ProcessFlow flow2 = new ProcessFlow();
flow2.setFormId(form.getFormId());
flow2.setOperatorId(dmanage.getEmployeeId());
flow2.setAction("audit");//审批任务
flow2.setCreateTime(new Date());
flow2.setOrderNo(2);
flow2.setState("process");
long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
float hours = diff/(1000*60*60) * 1f;
if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
flow2.setIsLast(0); //最后节点
processFlowDao.insert(flow2);
Employee manager = employeeDao.selectLeader(dmanage);//总经理
ProcessFlow flow3 = new ProcessFlow();
flow3.setFormId(form.getFormId());
flow3.setOperatorId(manager.getEmployeeId());
flow3.setAction("audit");
flow3.setCreateTime(new Date());
flow3.setState("ready");
flow3.setOrderNo(3);
flow3.setIsLast(1);
processFlowDao.insert(flow3);
}else {//小于3天{
flow2.setIsLast(1);
processFlowDao.insert(flow2);
}
} else if (employee.getLevel() == 7) {//部门经理
//3.2 7级员工,生成总经理审批任务
Employee manager = employeeDao.selectLeader(employee);
ProcessFlow flow = new ProcessFlow();
flow.setFormId(form.getFormId());
flow.setOperatorId(manager.getEmployeeId());
flow.setAction("audit");
flow.setCreateTime(new Date());
flow.setState("process");
flow.setOrderNo(2);
flow.setIsLast(1);
processFlowDao.insert(flow);
}else if (employee.getLevel() == 8){
//3.3 8级员工,生成总经理审批任务,系统自动通过
ProcessFlow flow = new ProcessFlow();
flow.setFormId(form.getFormId());
flow.setOperatorId(employee.getEmployeeId());
flow.setAction("audit");
flow.setResult("自动通过");
flow.setCreateTime(new Date());
flow.setAuditTime(new Date());
flow.setState("complete");
flow.setOrderNo(2);
flow.setIsLast(1);
processFlowDao.insert(flow);
}
return form;
});
return savedForm;
}
}
service.BussinessConstants.java
package com.imooc.oa.service;
public class BussinessConstants {
public static final int MANAGER_AUDIT_HOURS = 36; //总经理请假审批时间阈值
}
ctrl+shift+t 生成测试用例
Test.service.LeaveFormServiceTest.java
package com.imooc.oa.service;
import com.imooc.oa.entity.LeaveForm;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.junit.Assert.*;
public class LeaveFormServiceTest {
LeaveFormService leaveFormService = new LeaveFormService();
/**
* 市场部员工请假单(72小时以上)测试用例
* @throws ParseException
*/
@Test
public void createLeaveForm1() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
LeaveForm form = new LeaveForm();
form.setEmployeeId(8l);
form.setStartTime(sdf.parse("2020032608"));
form.setEndTime(sdf.parse("2020040118"));
form.setFormType(1); //事假
form.setReason("市场部员工请假单(72小时以上)");
form.setCreateTime(new Date());
LeaveForm savedForm = leaveFormService.createLeaveForm(form);
System.out.println(savedForm.getFormId());
}
/**
* 市场部员工请假单(72小时内)测试用例
* @throws ParseException
*/
@Test
public void createLeaveForm2() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
LeaveForm form = new LeaveForm();
form.setEmployeeId(8l);
form.setStartTime(sdf.parse("2020032608"));
form.setEndTime(sdf.parse("2020032718"));
form.setFormType(1);
form.setReason("市场部员工请假单(72小时内)");
form.setCreateTime(new Date());
LeaveForm savedForm = leaveFormService.createLeaveForm(form);
System.out.println(savedForm.getFormId());
}
/**
* 研发部部门经理请假单测试用例
* @throws ParseException
*/
@Test
public void createLeaveForm3() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
LeaveForm form = new LeaveForm();
form.setEmployeeId(2l);
form.setStartTime(sdf.parse("2020032608"));
form.setEndTime(sdf.parse("2020040118"));
form.setFormType(1);
form.setReason("研发部部门经理请假单");
form.setCreateTime(new Date());
LeaveForm savedForm = leaveFormService.createLeaveForm(form);
System.out.println(savedForm.getFormId());
}
/**
* 总经理请假单测试用例
* @throws ParseException
*/
@Test
public void createLeaveForm4() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
LeaveForm form = new LeaveForm();
form.setEmployeeId(1l);
form.setStartTime(sdf.parse("2020032608"));
form.setEndTime(sdf.parse("2020040118"));
form.setFormType(1);
form.setReason("总经理请假单");
form.setCreateTime(new Date());
LeaveForm savedForm = leaveFormService.createLeaveForm(form);
System.out.println(savedForm.getFormId());
}
}
实现请假申请控制
Servlet 前后端整体交互(底层)
leaveformservlet
controller.LeaveFormServlet.java
package com.imooc.oa.controller;
import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.LeaveFormService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "LeaveFormServlet", urlPatterns = "/leave/*")
public class LeaveFormServlet extends HttpServlet {
private LeaveFormService leaveFormService = new LeaveFormService();
private Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
// http://localhost/leave/create
String uri = request.getRequestURI();
String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
if (methodName.equals("create")){
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
this.doPost(request,response);
}
/**
* 创建请假单
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("login_user");
String formType = request.getParameter("formType");
String strStartTime = request.getParameter("startTime");
String strEndTime = request.getParameter("endTime");
String reason = request.getParameter("reason");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
Map result = new HashMap();
try {
LeaveForm form = new LeaveForm();
form.setEmployeeId(user.getEmployeeId());
form.setStartTime(sdf.parse(strStartTime));
form.setEndTime(sdf.parse(strEndTime));
form.setFormType(Integer.parseInt(formType));
form.setReason(reason);
form.setCreateTime(new Date());
//2.调用业务逻辑方法
leaveFormService.createLeaveForm(form);
result.put("code", "0");
result.put("message", "success");
} catch (Exception e){
logger.error("请假申请异常",e);
result.put("code", e.getClass().getSimpleName());
result.put("message", e.getMessage());
}
//3.组织相应数据
String json = JSON.toJSONString(result); //将result转换为字符串
response.getWriter().println(json);
}
}
完整实现请假申请功能
controller.ForwardServlet.java
package com.imooc.oa.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 页面跳转Servlet
*/
@WebServlet(name="ForwardServlet", urlPatterns = "/forward/*")
public class ForwardServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
/** 动态提取 把第一个/抛出外 再去寻找第一个‘/’
* /forward/form
* /forward/a/b/c/form
*/
String subUri = uri.substring(1);
String page = subUri.substring(subUri.indexOf("/"));
request.getRequestDispatcher(page + ".ftl").forward(request,response); //扩展名 + web.xml映射路径
}
}
form.html 变换为 form.ftl 放在ftl内
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>请假申请</title>
<link rel="stylesheet" href="/resources/layui/css/layui.css">
<style>
/*表单容器*/
.ns-container {
position: absolute;
width: 500px;
height: 450px;
top: 150px;
left: 50%;
margin-left: -250px;
padding: 20px;
box-sizing: border-box;
border: 1px solid #cccccc;
}
</style>
</head>
<body>
<div class="layui-row">
<blockquote class="layui-elem-quote">
<h2>请假申请</h2>
</blockquote>
<table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>
<div class="ns-container">
<h1 style="text-align: center;margin-bottom: 20px">请假申请单</h1>
<form class="layui-form">
<!--基本信息-->
<div class="layui-form-item">
<label class="layui-form-label">部门</label>
<div class="layui-input-block">
<div class="layui-col-md12" style="padding-top: 10px;">
研发部
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">申请人</label>
<div class="layui-input-block">
<div class="layui-col-md12" style="padding-top: 10px;">
${current_employee.name}[${current_employee.title}]
</div>
</div>
</div>
<!--请假类型下拉框-->
<div class="layui-form-item">
<label class="layui-form-label">请假类别</label>
<div class="layui-input-block layui-col-space5">
<select name="formType" lay-verify="required" lay-filter="cityCode">
<option value="1">事假</option>
<option value="2">病假</option>
<option value="3">工伤假</option>
<option value="4">婚嫁</option>
<option value="5">产假</option>
<option value="6">丧假</option>
</select>
</div>
</div>
<!--请假时长日期选择框-->
<div class="layui-form-item">
<label class="layui-form-label">请假时长</label>
<div class="layui-input-block layui-col-space5">
<input name="leaveRange" type="text" class="layui-input" id="daterange" placeholder=" - ">
<input id="startTime" name="startTime" type="hidden">
<input id="endTime" name="endTime" type="hidden">
</div>
</div>
<!--请假事由-->
<div class="layui-form-item">
<label class="layui-form-label">请假事由</label>
<div class="layui-input-block layui-col-space5">
<input name="reason" type="text" lay-verify="required|mobile" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<!--提交按钮-->
<div class="layui-form-item " style="text-align: center">
<button class="layui-btn" type="button" lay-submit lay-filter="sub">立即申请</button>
</div>
</form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<!--Sweetalert2对话框-->
<script src="/resources/sweetalert2.all.min.js"></script>
<script>
var layDate = layui.laydate; //Layui日期选择框JS对象
var layForm = layui.form; //layui表单对象
var $ = layui.$; //jQuery对象
//日期时间范围
layDate.render({
elem: '#daterange' //daterange渲染成日期选择框
,type: 'datetime'
,range: true
,format: 'yyyy年M月d日H时'
,done: function(value, start, end){
//选择日期后出发的时间,设置startTime与endTime隐藏域
var startTime = start.year + "-" + start.month + "-" + start.date + "-" + start.hours;
var endTime = end.year + "-" + end.month + "-" + end.date + "-" + end.hours;
console.info("请假开始时间",startTime);
$("#startTime").val(startTime);
console.info("请假结束时间",endTime);
$("#endTime").val(endTime);
}
});
//表单提交时间
layForm.on('submit(sub)', function(data){
console.info("向服务器提交的表单数据",data.field);
$.post("/leave/create",data.field,function (json) {
console.info("服务器返回数值",json);
if(json.code == "0"){
/*SweetAlert2确定对话框*/
swal({
type: 'success',
html: "<h2>请假单已提交,等待上级审批</h2>",
confirmButtonText: "确定"
}).then(function (result) {
window.location.href="/forward/notice";
});
}else{
swal({
type: 'warning',
html: "<h2>" + json.message + "</h2>",
confirmButtonText: "确定"
});
}
},"json");
return false;
});
</script>
</body>
</html>
把localhost/index 主页添加数据
index.ftl中的47行到50行
<!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
<a href="#{node.url}" target="ifmMain">${node.nodeName}</a>
</dd>
请假审批功能
leave_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.LeaveFormDao">
<insert id="insert" parameterType="com.imooc.oa.entity.LeaveForm"
useGeneratedKeys="true" keyProperty="formId" keyColumn="form_id">
INSERT INTO adm_leave_form( employee_id, form_type, start_time, end_time, reason, create_time, state)
VALUES ( #{employeeId}, #{formType}, #{startTime}, #{endTime}, #{reason}, #{createTime}, #{state})
</insert>
<select id="selectByParams" parameterType="java.util.Map" resultType="java.util.Map">
select f.* ,e.name , d.*
from
adm_leave_form f,adm_process_flow pf , adm_employee e , adm_department d
where
f.form_id = pf.form_id
and f.employee_id = e.employee_id
and e.department_id = d.department_id
and pf.state = #{pf_state} and pf.operator_id = #{pf_operator_id}
</select>
<!-- <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.LeaveForm">-->
<!-- select * from adm_leave_form where form_id = #{value}-->
<!-- </select>-->
<!-- <update id="update" parameterType="com.imooc.oa.entity.LeaveForm">-->
<!-- UPDATE adm_leave_form SET employee_id = #{employeeId} , form_type = #{formType}, start_time = #{startTime}, end_time = #{endTime}, reason = #{reason}, state = #{state} ,create_time = #{createTime} WHERE form_id = #{formId}-->
<!-- </update>-->
</mapper>
dao.LeaveFormDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.LeaveForm;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface LeaveFormDao {
public void insert(LeaveForm form);
public List<Map> selectByParams(@Param("pf_state") String pfState , @Param("pf_operator_id") Long operatorId);
}
Test
@Test
public void testSelectByParams(){
MybatisUtils.executeQuery(sqlSession -> {
LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
List<Map> list = dao.selectByParams("process", 21L);
System.out.println(list);
return list;
});
}
controller.LeaveFormServlet.java
/**
* 查询需要审核的请假单列表
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void getLeaveFormList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
User user = (User) request.getSession().getAttribute("login_user");
List<Map> formList = leaveFormService.getLeaveFormList("process", user.getEmployeeId());
Map result = new HashMap();
result.put("code","0"); //服务器处理成功
result.put("msg",""); //服务器返回具体处理消息
result.put("count", formList.size()); //数据总数
result.put("data", formList); //当前显示的对象页表
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
实现待审批请假列表
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>请假审批</title>
<link rel="stylesheet" href="/resources/layui/css/layui.css">
<style>
.form-item{
padding: 10px;
}
.form-item-value{
padding: 10px;
}
</style>
</head>
<body>
<div class="layui-row">
<blockquote class="layui-elem-quote">
<h1>请假审批</h1>
</blockquote>
<!--待审批列表-->
<table id="grdFormList" lay-filter="grdFormList"></table>
</div>
<!--请假详情对话框-->
<div id="divDialog" style="display: none;padding: 10px">
<form class="layui-form">
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs2 form-item">部门</div>
<div class="layui-col-xs4 form-item-value" id="dname"></div>
<div class="layui-col-xs2 form-item">姓名</div>
<div class="layui-col-xs4 form-item-value" id="name"></div>
</div>
<div class="layui-row">
<div class="layui-col-xs2 form-item">起始时间</div>
<div class="layui-col-xs4 form-item-value" id="startTime"></div>
<div class="layui-col-xs2 form-item">结束时间</div>
<div class="layui-col-xs4 form-item-value" id="endTime"></div>
</div>
<div class="layui-row">
<div class="layui-col-xs2 form-item">请假原因</div>
<div class="layui-col-xs10 form-item-value" id="reason"></div>
</div>
<!--表单Id-->
<input type="hidden" name="formId" id="formId">
<!--审批结果-->
<select name="result" lay-verfity="required">
<option value="approved">同意</option>
<option value="refused">驳回</option>
</select>
</div>
<div class="layui-form-item">
<!--审批意见-->
<input type="text" name="reason" placeholder="请输入审批意见"
autocomplete="off" class="layui-input"/>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid " lay-submit lay-filter="audit">确认提交</button>
</div>
</form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script src="/resources/sweetalert2.all.min.js"></script>
<script>
var $ = layui.$;
//将毫秒数转换为"yyyy-MM-dd HH时"字符串格式
function formatDate(time){
var newDate = new Date(time);
return newDate.getFullYear() + "-" +
(newDate.getMonth() + 1) + "-" + newDate.getDate()
+ " " + newDate.getHours() + "时";
}
// 将table渲染为数据表格
layui.table.render({
elem : "#grdFormList" , //选择器
id : "grdFormList" , //id
url : "/leave/list" , //ajax请求url
page : false , //是否分页 true-是 false-否
cols :[[ //列描述
{title : "" , width:70 , style : "height:60px" , type:"numbers"}, // numbers代表序号列
{field : "create_time" , title : "申请时间" , width : 150 , templet: function (d) {
//templet代表对数据进行加工后再显示
return formatDate(d.create_time)
}},
{field : "form_type" , title : "类型" , width : 100 , templet: function(d){
switch (d.form_type) {
case 1:
return "事假";
case 2:
return "病假";
case 3:
return "工伤假";
case 4:
return "婚假";
case 5:
return "产假";
case 6:
return "丧假";
}
}},
{field : "department_name" , title : "部门" , width : 100},
{field : "name" , title : "员工" , width : 100},
{field : "start_time" , title : "起始时间" , width : 150, templet: function (d) {
return formatDate(d.start_time)
}},
{field : "end_time" , title : "结束时间" , width : 150 , templet: function (d) {
return formatDate(d.end_time)
}},
{field : "reason" , title : "请假原因" , width : 350 },
{title : "" , width:150 ,type:"space" , templet : function(d){
var strRec = JSON.stringify(d);
console.info("请假单数据", d);
console.info("请假单数据", strRec);
//将请假单数据存放至data-laf属性中
return "<button class='layui-btn layui-btn-danger layui-btn-sm btn-audit' data-laf=" + strRec + " >审批</button>";
}}
]]
})
// 绑定每一行的审批按钮
$(document).on("click" , ".btn-audit" , function(){
//初始化表单
$("#divDialog form")[0].reset();
$("#divDialog form form-item-value").text("");
//获取当前点击按钮的请假单数据,回填至显示项 json对象 内置数据显示页面
var laf = $(this).data("laf");
$("#dname").text(laf.department_name);
$("#name").text(laf.name);
$("#startTime").text(formatDate(laf.start_time));
$("#endTime").text(formatDate(laf.end_time));
$("#reason").text(laf.reason);
$("#formId").val(laf.form_id);
//弹出layui对话框
layui.layer.open({
type : "1" , //页面层
title : "请假审批" , //标题
content : $("#divDialog") , //指定对话框容器对象
area : ["500px" , "400px"] , //尺寸
end : function(){ //销毁后触发事件
$("#divDialog").hide();
}
})
})
/**
* 提交审批数据 本质:发送Ajax请求
*/
layui.form.on("submit(audit)" , function(data){
$.ajax({
url : "/leave/audit", //审核URL
data : data.field ,
type : "post" ,
dataType : "json" ,
success: function (json) {
//关闭所有layui对话框
layui.layer.closeAll();
//显示处理结果
if(json.code == "0"){
swal({
type: 'success',
html: "<h2>请假已审批完毕</h2>",
confirmButtonText: "确定"
}).then(function (result) {
window.location.href="/forward/notice";
});
}else{
swal({
type: 'warning',
html: "<h2>" + json.message + "</h2>",
confirmButtonText: "确定"
});
}
}
})
return false;
})
</script>
</body>
</html>
实现审批业务逻辑
service.LeaveFormService.java
public void audit(Long formId, Long operatorId, String result, String reason){
MybatisUtils.executeQuery(sqlSession -> {
//1.无论同意/驳回, 当前任务状态变更为complete
ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
if (flowList.size() == 0){
throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
}
//获取当前任务ProcessFlow对象
List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
ProcessFlow process = null;
if (processList.size() == 0){
throw new BussinessException("PF002", "未找到待处理任务");
}else{
process = processList.get(0);
process.setState("complete");
process.setResult(result);
process.setReason(reason);
process.setAuditTime(new Date());
processFlowDao.update(process);
}
LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
LeaveForm form = leaveFormDao.selectById(formId);
//2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
if (process.getIsLast() == 1){
form.setState(result); // approved\refused
leaveFormDao.update(form);
}else {
//readyList包含所有后续任务节点
List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
//3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
if (result.equals("approved")){
ProcessFlow readyProcess = readyList.get(0);
readyProcess.setState("process");
processFlowDao.update(readyProcess);
} else if (result.equals("refused")) {
//4.如果当前任务不是最后一个切点且审核驳回,则后续所有任务状态变为cancel,请假单状态变为refused
for (ProcessFlow p : readyList){
p.setState("cancel");
processFlowDao.update(p);
}
form.setState("refused");
leaveFormDao.update(form);
}
}
return null;
});
}
test.LeaveFormServiceTest.java (在数据库中增加新建查询 导入训练素材的 请假单审核测试数据.sql)
/**
* 请假3天以上,部门经理审批通过
*/
@Test
public void audit1(){
leaveFormService.audit(31l,2l,"approved","祝早日康复");
}
/**
* 请假3天以上,部门经理审批驳回
*/
@Test
public void audit2(){
leaveFormService.audit(32l,2l,"refused","工期紧张,请勿拖延");
}
/**
* 部门经理请假,总经理审批通过
*/
@Test
public void audit3(){
leaveFormService.audit(33l,1l,"approved","同意");
}
完整实现请假审批
controller.LeaveFormServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
// http://localhost/leave/create
String uri = request.getRequestURI();
String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
if (methodName.equals("create")){
this.create(request,response);
}else if (methodName.equals("list")){
this.getLeaveFormList(request,response);
} else if (methodName.equals("audit")) {
this.audit(request,response);
}
}
...
...
...
/**
* 处理审批操作
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void audit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String formId = request.getParameter("formId");
String result = request.getParameter("result");
String reason = request.getParameter("reason");
User user = (User) request.getSession().getAttribute("login_user");
Map mpResult = new HashMap();
try {
leaveFormService.audit(Long.parseLong(formId), user.getEmployeeId(), result,reason);
mpResult.put("code", "0");
mpResult.put("message", "success");
} catch (Exception e) {
logger.error("请假单审核失败", e);
mpResult.put("code", e.getClass().getSimpleName());
mpResult.put("message", e.getMessage());
}
String json = JSON.toJSONString(mpResult);
response.getWriter().println(json);
}
实现系统消息业务逻辑
entity.Notice.java
private Long noticeId;
private Long receiverId;
private String content;
private Date createTime;
public Notice(){
}
public Notice(Long receiverId, String content){
this.receiverId = receiverId;
this.content = content;
this.createTime = new Date();
}
Getter + Setter
service.LeaveFormService.java
package com.imooc.oa.service;
import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MybatisUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 请假单流程服务
*/
public class LeaveFormService {
/**
* 创建请假单
* @param form 前端输入的请假单数据
* @return 持久化后的请假单对象
*/
public LeaveForm createLeaveForm(LeaveForm form){
LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
//1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
Employee employee = employeeDao.selectById(form.getEmployeeId());
if (employee.getLevel() == 8){
form.setState("approved");
}else {
form.setState("processing");
}
LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
leaveFormDao.insert(form);
//2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
ProcessFlow flow1 = new ProcessFlow();
flow1.setFormId(form.getFormId());
flow1.setOperatorId(employee.getEmployeeId());
flow1.setAction("apply");
flow1.setCreateTime(new Date());
flow1.setOrderNo(1);
flow1.setState("complete");
flow1.setIsLast(0);
processFlowDao.insert(flow1);
//3.分情况创建其余流程数据 employee.xml(动态sql语句)
// 3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
if (employee.getLevel() < 7){
//动态sql EmployeeDao
Employee dmanager = employeeDao.selectLeader(employee);
ProcessFlow flow2 = new ProcessFlow();
flow2.setFormId(form.getFormId());
flow2.setOperatorId(dmanager.getEmployeeId());
flow2.setAction("audit");//审批任务
flow2.setCreateTime(new Date());
flow2.setOrderNo(2);
flow2.setState("process");
long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
float hours = diff/(1000*60*60) * 1f;
if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
flow2.setIsLast(0); //最后节点
processFlowDao.insert(flow2);
Employee manager = employeeDao.selectLeader(dmanager);//总经理
ProcessFlow flow3 = new ProcessFlow();
flow3.setFormId(form.getFormId());
flow3.setOperatorId(manager.getEmployeeId());
flow3.setAction("audit");
flow3.setCreateTime(new Date());
flow3.setState("ready");
flow3.setOrderNo(3);
flow3.setIsLast(1);
processFlowDao.insert(flow3);
}else {//小于3天{
flow2.setIsLast(1);
processFlowDao.insert(flow2);
}
//请假单已提交消息
String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
, sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
//通知部门经理审批消息
noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(dmanager.getEmployeeId(),noticeContent));
} else if (employee.getLevel() == 7) {//部门经理
//3.2 7级员工,生成总经理审批任务
Employee manager = employeeDao.selectLeader(employee);
ProcessFlow flow = new ProcessFlow();
flow.setFormId(form.getFormId());
flow.setOperatorId(manager.getEmployeeId());
flow.setAction("audit");
flow.setCreateTime(new Date());
flow.setState("process");
flow.setOrderNo(2);
flow.setIsLast(1);
processFlowDao.insert(flow);
//请假单已提交消息
String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
, sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
//通知总经理审批消息
noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(manager.getEmployeeId(),noticeContent));
}else if (employee.getLevel() == 8){
//3.3 8级员工,生成总经理审批任务,系统自动通过
ProcessFlow flow = new ProcessFlow();
flow.setFormId(form.getFormId());
flow.setOperatorId(employee.getEmployeeId());
flow.setAction("audit");
flow.setResult("自动通过");
flow.setCreateTime(new Date());
flow.setAuditTime(new Date());
flow.setState("complete");
flow.setOrderNo(2);
flow.setIsLast(1);
processFlowDao.insert(flow);
String noticeContent = String.format("您的请假申请[%s-%s]系统已自动批准通过." ,
sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
}
return form;
});
return savedForm;
}
/**
* 获取指定任务状态及指定经办人对应的请假单列表
* @param pfState ProcessFlow任务状态
* @param operatorId 经办人编号
* @return 请假单及相关数据列表
*/
public List<Map> getLeaveFormList(String pfState, Long operatorId){
return (List<Map>)MybatisUtils.executeQuery(sqlSession -> {
LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
List<Map> formList = dao.selectByParams(pfState, operatorId);
return formList;
});
}
/**
* 审核请假单
* @param formId 表单编号
* @param operatorId 经办人(当前登录员工)
* @param result 审批结果
* @param reason 审批意见
*/
public void audit(Long formId, Long operatorId, String result, String reason){
MybatisUtils.executeQuery(sqlSession -> {
//1.无论同意/驳回, 当前任务状态变更为complete
ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
if (flowList.size() == 0){
throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
}
//获取当前任务ProcessFlow对象
List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
ProcessFlow process = null;
if (processList.size() == 0){
throw new BussinessException("PF002", "未找到待处理任务");
}else{
process = processList.get(0);
process.setState("complete");
process.setResult(result);
process.setReason(reason);
process.setAuditTime(new Date());
processFlowDao.update(process);
}
LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
LeaveForm form = leaveFormDao.selectById(formId);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
Employee employee = employeeDao.selectById(form.getEmployeeId());//表单提交人信息
Employee operator = employeeDao.selectById(operatorId);//任务经办人信息
NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
//2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
if (process.getIsLast() == 1){
form.setState(result); // approved\refused
leaveFormDao.update(form);
String strResult = null;
if (result.equals("aproved")){
strResult = "批准";
} else if (result.equals("refused")) {
strResult = "驳回";
}
String noticeContent = String.format("您的请假申请[%s-%s]%s%s已%s,审批意见:%s,审批流程已结束"
sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
operator.getTitle(),operator.getName(), //批准\驳回
strResult,reason);//发给表单提交人的通知
noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent));
}else {
//readyList包含所有后续任务节点
List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
//3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
if (result.equals("approved")){
ProcessFlow readyProcess = readyList.get(0);
readyProcess.setState("process");
processFlowDao.update(readyProcess);
//消息1: 通知表单提交人,部门经理已经审批通过,交由上级继续审批
String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已批准,审批意见:%s ,请继续等待上级审批" ,
sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
operator.getTitle() , operator.getName(),reason);
noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));
//消息2: 通知总经理有新的审批任务
String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批" ,
employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(readyProcess.getOperatorId(),noticeContent2));
//消息3: 通知部门经理(当前经办人),员工的申请单你已批准,交由上级继续审批
String noticeContent3 = String.format("%s-%s提起请假申请[%s-%s]您已批准,审批意见:%s,申请转至上级领导继续审批" ,
employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent3));
} else if(result.equals("refused")) {
//4.如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
for(ProcessFlow p:readyList){
p.setState("cancel");
processFlowDao.update(p);
}
form.setState("refused");
leaveFormDao.update(form);
//消息1: 通知申请人表单已被驳回
String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已驳回,审批意见:%s,审批流程已结束" ,
sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
operator.getTitle() , operator.getName(),reason);
noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));
//消息2: 通知经办人表单"您已驳回"
String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s]您已驳回,审批意见:%s,审批流程已结束" ,
employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent2));
}
}
return null;
});
完整实现系统消息功能
dao.NoticeDao.java
package com.imooc.oa.dao;
import com.imooc.oa.entity.Notice;
import java.util.List;
public interface NoticeDao {
public void insert(Notice notice);
public List<Notice> selectByReceiverId(Long receiverId);
}
service.NoticeService.java
package com.imooc.oa.service;
import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;
import java.util.List;
/**
* 消息服务
*/
public class NoticeService {
/**
* 查询指定员工的系统消息
* @param receiverId
* @return 最近100条消息列表
*/
public List<Notice> getNoticeList(Long receiverId){
return (List) MybatisUtils.executeQuery(sqlSession -> {
NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
return noticeDao.selectByReceiverId(receiverId);
});
}
}
controller.NoticeServlet.java
package com.imooc.oa.controller;
import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.NoticeService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@WebServlet(name = "NoticeServlet" , urlPatterns = "/notice/list")
public class NoticeServlet extends HttpServlet {
private NoticeService noticeService = new NoticeService();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
User user = (User)request.getSession().getAttribute("login_user");
List<Notice> noticeList = noticeService.getNoticeList(user.getEmployeeId());
Map result = new HashMap<>();
result.put("code", "0");
result.put("msg", "");
result.put("count", noticeList.size());
result.put("data", noticeList);
String json = JSON.toJSONString(result);
response.setContentType("text/html;charset=utf-8");
response.getWriter().println(json);
}
}
resources.mappers.notice.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.NoticeDao">
<insert id="insert" parameterType="com.imooc.oa.entity.Notice"
useGeneratedKeys="true" keyProperty="noticeId" keyColumn="notice_id">
INSERT INTO sys_notice( receiver_id, content, create_time) VALUES (#{receiverId}, #{content}, #{createTime})
</insert>
<select id="selectByReceiverId" parameterType="Long" resultType="com.imooc.oa.entity.Notice">
select * from sys_notice where receiver_id = #{value} order by create_time desc limit 0,100
</select>
</mapper>
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>办公OA系统</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
<!--头部导航栏-->
<div class="layui-header">
<!--系统标题-->
<div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
<!--右侧当前用户信息-->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:void(0)">
<!--图标-->
<span class="layui-icon layui-icon-user" style="font-size: 20px">
</span>
<!--用户信息-->
${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
</a>
</li>
<!--注销按钮-->
<li class="layui-nav-item"><a href="/logout">注销</a></li>
</ul>
</div>
<!--左侧菜单栏-->
<div class="layui-side layui-bg-black">
<!--可滚动菜单-->
<div class="layui-side-scroll">
<!--可折叠导航栏-->
<ul class="layui-nav layui-nav-tree">
<#list node_list as node>
<!--父节点-->
<#if node.nodeType == 1>
<li class="layui-nav-item layui-nav-itemed">
<a href="javascript:void(0)">${node.nodeName}</a>
<dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
</li>
</#if>
<#if node.nodeType == 2>
<!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
<a href="${node.url}" target="ifmMain">${node.nodeName}</a>
</dd>
</#if>
</#list>
</ul>
</div>
</div>
<!--主体部分采用iframe嵌入其他页面-->
<div class="layui-body" style="overflow-y: hidden">
<iframe name="ifmMain" src="/forward/notice" style="border: 0px;width: 100%;height: 100%"></iframe>
</div>
<!--版权信息-->
<div class="layui-footer">
Copyright © imooc. All Rights Reserved.
</div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
//将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
layui.$(".function").each(function () {
var func = layui.$(this);
var parentId = func.data("parent-id");
layui.$("dl[data-node-id=" + parentId + "]").append(func);
})
//刷新折叠菜单
layui.element.render('nav');
</script>
</body>
</html>
notice.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>系统通知</title>
<link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body>
<div class="layui-row">
<blockquote class="layui-elem-quote">
<h2>系统通知</h2>
</blockquote>
<table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script>
layui.table.render({
elem : "#grdNoticeList" ,
id : "grdNoticeList" ,
url : "/notice/list" ,
page : false ,
cols :[[
{field : "" , title : "序号" , width:"10%" , style : "height:60px" , type:"numbers"},
{field : "create_time" , title : "通知时间" , width : "20%" , templet: function (d) {
var newDate = new Date(d.createTime);
return newDate.getFullYear() + "-" +
(newDate.getMonth() + 1) + "-" + newDate.getDate()
+ " " + newDate.getHours() + ":" + newDate.getMinutes() + ":" + newDate.getSeconds();
}},
{field : "content" , title : "通知内容" , width : "60%"}
]]
})
</script>
</body>
</html>